package common;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;

/**
 * Classe che permette di scrivere su stdout/stderr e su un file di log.
 * Esiste anche una classe adatta allo scopo, ma va be'...
 */
public class Logger {
	/**
	 * lo stream su cui scrivere il log
	 */
	public PrintStream logFile;
	
	/**
	 * Scrive su stdout e su logFile
	 */
	private PrintStream out;
	/**
	 * Scrive su stderr e su logFile
	 */
	private PrintStream err;
	
	/**
	 * Crea un nuovo logger che scrive nel file "../logs/filename"
	 * @param filename nome del file
	 * @throws FileNotFoundException
	 */
	public Logger(String filename) throws FileNotFoundException {
		//String workingDir = System.getProperty("user.dir");
		//System.out.println("Cartella corrente: " + workingDir);
		logFile= new PrintStream(new File("./logs/" + filename));
		out= new PrintStream(new DoubleOutputStream(System.out, logFile));
		err= new PrintStream(new DoubleOutputStream(System.err, logFile));
	}
	/**
	 * Crea un nuovo logger che non scrive su file, ma solo su stdout e stderr
	 */
	public Logger() {
		out= new PrintStream(System.out);
		err= new PrintStream(System.err);
	}
	/**
	 * Restituisco il PrintStream che scrive su stdout e su file di log.
	 * Da usare ad esempio quando si usa printStackTrace() 
	 * @return vedi descrizione
	 */
	public PrintStream getOutputPrintStream() {
		return out;
	}
	
	/**
	 * Restituisco il PrintStream che scrive su stderr e su file di log
	 * Da usare ad esempio quando si usa printStackTrace() 
	 * @return vedi descrizione
	 */
	public PrintStream getErrorPrintStream() {
		return err;
	}
	
	/**
	 * Stampa su stdout e su file di log
	 * @param str stringa da stampare
	 */
	public synchronized void print(String str) {
		out.println(str);
		out.flush();
	}
	/**
	 * Stampa su stdout e su file di log una riga vuota
	 */
	public synchronized void print() {
		out.println();
		out.flush();
	}
	/**
	 * Stampa su stderr e su file di log
	 * @param str stringa da stampare
	 */
	public synchronized void printErr(String str) {
		err.println(str);
		err.flush();
	}
	/**
	 * Stampa su stderr e su file di log una riga vuota
	 */
	public synchronized void printErr() {
		err.println();
		err.flush();
	}
	/**
	 * Stampa solo su file di log
	 * @param str stringa da stampare
	 */
	public synchronized void printLog(String str) {
		logFile.println(str);
		logFile.flush();
	}
	/**
	 * Stampa solo su file di log una riga vuota
	 */
	public synchronized void printLog() {
		logFile.println();
		logFile.flush();
	}
	
	/**
	 * Stampa su stdout e su file di log aggiungendo il nome del thread
	 * @param str stringa da stampare
	 */
	public synchronized void printTh(String str) {
		String tName= Thread.currentThread().getName();
		print(tName + ": " + str);
	}
	/**
	 * Stampa su stderr e su file di log aggiungendo il nome del thread
	 * @param str stringa da stampare
	 */
	public synchronized void printErrTh(String str) {
		String tName= Thread.currentThread().getName();
		printErr(tName + ": " + str);
	}
	/**
	 * Stampa solo su file di log aggiungendo il nome del thread
	 * @param str stringa da stampare
	 */
	public synchronized void printLogTh(String str) {
		String tName= Thread.currentThread().getName();
		printLog(tName + ": " + str);
	}
	
	/**
	 * Scrive su due PrintStream contemporaneamente
	 */
	private class DoubleOutputStream extends FilterOutputStream {

	    private PrintStream file;

	    public DoubleOutputStream(PrintStream std, PrintStream file) {
	        super(std);
	        this.file= file;
	    }

	    public synchronized void write(int b) throws IOException {
	        out.write(b);
	        file.write(b);
	    }

	    public synchronized void write(byte b[]) throws IOException {
	        write(b, 0, b.length);
	    }

	    public synchronized void write(byte b[], int off, int len) throws IOException {
	        if ((off | len | (b.length - (len + off)) | (off + len)) < 0)
	            throw new IndexOutOfBoundsException();

	        for (int i = 0 ; i < len ; i++) {
	            write(b[off + i]);
	        }
	    }

	    public synchronized void flush() throws IOException {
	        out.flush();
	        file.flush();
	    }


	    public synchronized void close() throws IOException {
	        try {
	          flush();
	        } catch (IOException ignored) {
	        }
	        out.close();
	        file.close();
	    }
	}
}
